{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "7d389def-75f7-499e-a587-0e82b8bb636a",
   "metadata": {},
   "source": [
    "# Loading and rendering GLTF files\n",
    "You can [import meshes from GLTF](https://kaolin.readthedocs.io/en/latest/modules/kaolin.io.gltf.html) and render them and plug the renderer into Kaolin's [interactive renderer](https://kaolin.readthedocs.io/en/latest/modules/kaolin.visualize.html)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "ab6e3188-0186-463b-b915-183067e90842",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Note: you may need to restart the kernel to use updated packages.\n"
     ]
    }
   ],
   "source": [
    "pip install matplotlib --quiet"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "938466b1",
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "import glob\n",
    "import math\n",
    "import copy\n",
    "import os\n",
    "\n",
    "import torch\n",
    "from matplotlib import pyplot as plt\n",
    "from tutorial_common import COMMON_DATA_DIR\n",
    "\n",
    "import kaolin as kal"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d18717b0-d84e-4a7d-8f9c-1a067c0dd507",
   "metadata": {},
   "source": [
    "## Loading a mesh\n",
    "To import mesh data and materials into PyTorch tensors, simply call [kaolin.io.gltf.import_mesh](https://kaolin.readthedocs.io/en/latest/modules/kaolin.io.gltf.html#kaolin.io.gltf.import_mesh). The loaded data is returned as [SurfaceMesh class](https://kaolin.readthedocs.io/en/latest/modules/kaolin.rep.surface_mesh.html#kaolin.rep.SurfaceMesh) with contained materials of type [PBRMaterial class](https://kaolin.readthedocs.io/en/latest/modules/kaolin.render.materials.html#kaolin.render.materials.PBRMaterial). These containers make it very easy to inspect and preprocess the data.\n",
    "\n",
    "You can also try loading other [GLTF sample models](https://github.com/KhronosGroup/glTF-Sample-Models/)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "41eb96d6",
   "metadata": {
    "tags": []
   },
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "/home/mshugrina/anaconda3/envs/kaolin-dev/lib/python3.8/site-packages/pygltflib-1.16.1-py3.8.egg/pygltflib/__init__.py:900: UserWarning: Conversion will leave avocado.bin file orphaned since data is now in the GLTF object.\n",
      "/home/mshugrina/anaconda3/envs/kaolin-dev/lib/python3.8/site-packages/pygltflib-1.16.1-py3.8.egg/pygltflib/__init__.py:877: UserWarning: pygltflib currently unable to add image data to buffers.Please open an issue at https://gitlab.com/dodgyville/pygltflib/issues\n",
      "/home/mshugrina/Documents/Coding/Kaolin/Dev/kaolin/kaolin/io/gltf.py:266: UserWarning: The given buffer is not writable, and PyTorch does not support non-writable tensors. This means you can write to the underlying (supposedly non-writable) buffer using the tensor. You may want to copy the buffer to protect its data or make it writable before converting it to a tensor. This type of warning will be suppressed for the rest of this program. (Triggered internally at  ../torch/csrc/utils/tensor_new.cpp:1105.)\n",
      "  output = torch.frombuffer(\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "SurfaceMesh object with batching strategy NONE\n",
      "            vertices: [406, 3] (torch.float32)[cuda:0]  \n",
      "               faces: [682, 3] (torch.int64)[cuda:0]  \n",
      "                 uvs: [406, 2] (torch.float32)[cuda:0]  \n",
      "        face_uvs_idx: [682, 3] (torch.int64)[cuda:0]  \n",
      "      vertex_normals: [406, 3] (torch.float32)[cuda:0]  \n",
      "     vertex_tangents: [406, 3] (torch.float32)[cuda:0]  \n",
      "material_assignments: [682] (torch.int16)[cuda:0]  \n",
      "           materials: list of length 1\n",
      "       face_vertices: if possible, computed on access from: (faces, vertices)\n",
      "        face_normals: if possible, computed on access from: (normals, face_normals_idx) or (vertex_normals, faces) or (vertices, faces)\n",
      "            face_uvs: if possible, computed on access from: (uvs, face_uvs_idx)\n",
      "       vertex_colors: if possible, computed on access from: (faces, face_colors)\n",
      "     vertex_features: if possible, computed on access from: (faces, face_features)\n",
      "       face_tangents: if possible, computed on access from: (faces, vertex_tangents)\n",
      "         face_colors: if possible, computed on access from: (faces, vertex_colors)\n",
      "       face_features: if possible, computed on access from: (faces, vertex_features)\n",
      "\n",
      "First material: \n",
      " PBRMaterial object with\n",
      "                    material_name: 2256_Avocado_d\n",
      "                      shader_name: UsdPreviewSurface\n",
      "                  diffuse_texture: [2048, 2048, 3] (torch.float32)[cuda:0]  \n",
      "                roughness_texture: [2048, 2048, 1] (torch.float32)[cuda:0]  \n",
      "                 metallic_texture: [2048, 2048, 1] (torch.float32)[cuda:0]  \n",
      "                  normals_texture: [2048, 2048, 3] (torch.float32)[cuda:0]  \n"
     ]
    }
   ],
   "source": [
    "path = os.path.join(COMMON_DATA_DIR, 'meshes', 'avocado.gltf')\n",
    "mesh = kal.io.gltf.import_mesh(path)\n",
    "\n",
    "mesh = mesh.cuda()\n",
    "mesh.vertices = kal.ops.pointcloud.center_points(\n",
    "    mesh.vertices.unsqueeze(0), normalize=True).squeeze(0)\n",
    "\n",
    "print(mesh)\n",
    "print(f'\\nFirst material: \\n {mesh.materials[0]}')"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8040fbf3-dfe0-4cbe-abb1-03d84524ed2a",
   "metadata": {},
   "source": [
    "## Rendering function and parameters\n",
    "Let's initialize simple [Spherical Gaussian](https://kaolin.readthedocs.io/en/latest/modules/kaolin.render.lighting.html)\n",
    "lighting, and a [Camera](https://kaolin.readthedocs.io/en/latest/modules/kaolin.render.camera.camera.html#kaolin.render.camera.Camera). \n",
    "\n",
    "To use the interactive visualizer, we must implement a rendering function that take a camera as input. Kaolin provides a standard shader `kal.render.easy_render.render_mesh` that can render imported `gltf` files with just one line."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "277c8df7-a19f-493a-af1d-64dd78f7bd3e",
   "metadata": {
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "CameraExtrinsics of 1 cameras, of coordinate system: \n",
      "tensor([[1., 0., 0.],\n",
      "        [0., 1., 0.],\n",
      "        [0., 0., 1.]]). \n",
      "Camera #0 View Matrix: \n",
      "tensor([[[ 0.7071,  0.0000, -0.7071,  0.0000],\n",
      "         [-0.4082,  0.8165, -0.4082,  0.0000],\n",
      "         [ 0.5774,  0.5774,  0.5774, -1.7321],\n",
      "         [ 0.0000,  0.0000,  0.0000,  1.0000]]], device='cuda:0'),\n",
      "Camera #0 Inverse View Matrix: \n",
      "tensor([[[ 0.7071, -0.4082,  0.5774,  1.0000],\n",
      "         [ 0.0000,  0.8165,  0.5774,  1.0000],\n",
      "         [-0.7071, -0.4082,  0.5774,  1.0000],\n",
      "         [ 0.0000,  0.0000,  0.0000,  1.0000]]], device='cuda:0')\n",
      "\n",
      "PinholeIntrinsics of 1 cameras of resolution 512x512.\n",
      "Camera #0: {'x0': 0.0, 'y0': 0.0, 'focal_x': 618.0386962890625, 'focal_y': 618.0386962890625}\n",
      "\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "<matplotlib.image.AxesImage at 0x7ffa2cb899d0>"
      ]
     },
     "execution_count": 4,
     "metadata": {},
     "output_type": "execute_result"
    },
    {
     "data": {
      "image/png": "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",
      "text/plain": [
       "<Figure size 432x288 with 1 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "# Lighting parameters that are easy to control with sliders\n",
    "azimuth = torch.zeros((1,), device='cuda')\n",
    "elevation = torch.full((1,), math.pi / 3., device='cuda')\n",
    "amplitude = torch.full((1, 3), 3., device='cuda')\n",
    "sharpness = torch.full((1,), 5., device='cuda')\n",
    "\n",
    "def current_lighting():\n",
    "    \"\"\" Convert slider lighting parameters to paramater class used for rendering.\"\"\"\n",
    "    direction = kal.render.lighting.sg_direction_from_azimuth_elevation(azimuth, elevation)\n",
    "    return kal.render.lighting.SgLightingParameters(\n",
    "        amplitude=amplitude, sharpness=sharpness, direction=direction)\n",
    "\n",
    "# Camera\n",
    "camera = kal.render.easy_render.default_camera(512).cuda()\n",
    "print(camera)\n",
    "\n",
    "# Rendering\n",
    "active_pass=kal.render.easy_render.RenderPass.render\n",
    "def render(camera):\n",
    "    \"\"\"Render the mesh and its bundled materials.\n",
    "    \n",
    "    This is the main function provided to the interactive visualizer\n",
    "    \"\"\"\n",
    "    render_res = kal.render.easy_render.render_mesh(camera, mesh, lighting=current_lighting())\n",
    "    img = render_res[active_pass]\n",
    "    return {\"img\": (torch.clamp(img, 0., 1.)[0] * 255.).to(torch.uint8),\n",
    "            \"normals\": render_res[kal.render.easy_render.RenderPass.normals][0]}\n",
    "    \n",
    "def lowres_render(camera):\n",
    "    \"\"\"Render with lower dimension.\n",
    "    \n",
    "    This function will be used as a \"fast\" rendering used when the mouse is moving to avoid slow down.\n",
    "    \"\"\"\n",
    "    lowres_cam = copy.deepcopy(camera)\n",
    "    lowres_cam.width = camera.width // 8\n",
    "    lowres_cam.height = camera.height // 8\n",
    "    return render(lowres_cam)\n",
    "\n",
    "output = render(camera)\n",
    "plt.figure()\n",
    "plt.imshow(output['img'].cpu().numpy())"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e2023c19-0b6c-4aef-a491-0fa8b29fd5c0",
   "metadata": {},
   "source": [
    "## Interactive visualizer\n",
    "To interactively inspect rendering from all sides, we can now plug the camera and the renderer into the interactive visualizer, adding some ipywidgets interactive sliders to modify lighting."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "091083de",
   "metadata": {
    "tags": []
   },
   "outputs": [
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "265412d638674ae3b2e758ec98dcb426",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "HBox(children=(Canvas(height=512, width=512), interactive(children=(FloatSlider(value=1.0471975803375244, desc…"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "0b26903a52ca4622a53aa0776546f08e",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "Output()"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "from ipywidgets import interactive, HBox, FloatSlider\n",
    "\n",
    "visualizer = kal.visualize.IpyTurntableVisualizer(\n",
    "    512, 512, copy.deepcopy(camera), render, fast_render=lowres_render, \n",
    "    max_fps=5, world_up_axis=1, img_format='jpeg', img_quality=75\n",
    ")\n",
    "\n",
    "def sliders_callback(new_elevation, new_azimuth, new_amplitude, new_sharpness):\n",
    "    \"\"\"ipywidgets sliders callback\"\"\"\n",
    "    with visualizer.out: # This is in case of bug\n",
    "        elevation[:] = new_elevation\n",
    "        azimuth[:] = new_azimuth\n",
    "        amplitude[:] = new_amplitude\n",
    "        sharpness[:] = new_sharpness\n",
    "        # this is how we request a new update\n",
    "        visualizer.render_update()\n",
    "        \n",
    "elevation_slider = FloatSlider(\n",
    "    value=elevation.item(),\n",
    "    min=-math.pi / 2.,\n",
    "    max=math.pi / 2.,\n",
    "    step=0.1,\n",
    "    description='Elevation:',\n",
    "    continuous_update=True,\n",
    "    readout=True,\n",
    "    readout_format='.1f',\n",
    ")\n",
    "\n",
    "azimuth_slider = FloatSlider(\n",
    "    value=azimuth.item(),\n",
    "    min=-math.pi,\n",
    "    max=math.pi,\n",
    "    step=0.1,\n",
    "    description='Azimuth:',\n",
    "    continuous_update=True,\n",
    "    readout=True,\n",
    "    readout_format='.1f',\n",
    ")\n",
    "\n",
    "amplitude_slider = FloatSlider(\n",
    "    value=amplitude[0,0].item(),\n",
    "    min=0.1,\n",
    "    max=40.,\n",
    "    step=0.1,\n",
    "    description='Amplitude:\\n',\n",
    "    continuous_update=True,\n",
    "    readout=True,\n",
    "    readout_format='.1f',\n",
    ")\n",
    "\n",
    "sharpness_slider = FloatSlider(\n",
    "    value=sharpness.item(),\n",
    "    min=0.1,\n",
    "    max=20.,\n",
    "    step=0.1,\n",
    "    description='Sharpness:\\n',\n",
    "    continuous_update=True,\n",
    "    readout=True,\n",
    "    readout_format='.1f',\n",
    ")\n",
    "\n",
    "interactive_slider = interactive(\n",
    "    sliders_callback,\n",
    "    new_elevation=elevation_slider,\n",
    "    new_azimuth=azimuth_slider,\n",
    "    new_amplitude=amplitude_slider,\n",
    "    new_sharpness=sharpness_slider\n",
    ")\n",
    "\n",
    "# We combine all the widgets and the visualizer canvas and output in a single display\n",
    "full_output = HBox([visualizer.canvas, interactive_slider])\n",
    "display(full_output, visualizer.out)"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.9.19"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
